package com.chalmers.foa.gamehandlers.impl;

import java.util.List;

import android.graphics.Point;

import com.chalmers.foa.gamehandlers.ICollision;
import com.chalmers.foa.gamemodel.gameobjects.IGameObject;
import com.chalmers.foa.gamemodel.gameobjects.effects.impl.Particle;
import com.chalmers.foa.util.NewList;

public class CollisionHandler implements ICollision {

	private static CollisionHandler collisionHandler;
	private Point screenSize, startPos;
	private IGameObject[] objects;
	private IGameObject otherObject;
	private Point objectPosition, otherObjectPosition;

	public static CollisionHandler getInstance() {
		if (collisionHandler == null)
			collisionHandler = new CollisionHandler();
		return collisionHandler;
	}

	public CollisionHandler() {
		screenSize = new Point(480, 320); // Default screen size
		startPos = new Point(0, 0);
		objectPosition = new Point(0, 0);
		otherObjectPosition = new Point(0, 0);
	}

	public void setScreenSize(Point _size) {
		screenSize = new Point(_size);
	}

	public void setScreenSize(Point _size, Point _startPos) {
		setScreenSize(_size);
		startPos = new Point(_startPos);
	}

	/**
	 * Set the list of game objects. Usually done at the start of a level.
	 * 
	 * @param objects
	 *            A list of objects in a level.
	 */
	public void setObjectList(IGameObject[] objects) {
		this.objects = objects;
	}

	/**
	 * Given an offset this method returns a new offset that does not intersect
	 * with any solid objects.
	 * 
	 * @param object
	 *            The object for whom the offset shall be calculated for
	 * @param offset
	 *            The starting offset
	 * @return The new offset that does not intersect with any solid objects
	 */
	public void validateOffset(IGameObject object, Point offset) {
		validateOffset(object, offset, objects);
	}

	/**
	 * Given an offset this method returns a new offset that does not intersect
	 * with any solid objects in a specific list.
	 * 
	 * @param object
	 *            The object for whom the offset shall be calculated for
	 * @param offset
	 *            The starting offset
	 * @param collidingObjects
	 *            A list of object to check intersection with
	 * @return The new offset that does not intersect with any solid objects in
	 *         the specific list.
	 */
	public void validateOffset(IGameObject object, Point offset,
			IGameObject[] collidingObjects) {

		if (screenSize != null && objects != null) {
			alignOffsetIfOutsideScreen(object, offset);
			for (int i = 0; i < objects.length; i++) {
				otherObject = objects[i];
				if (otherObject.isSolid() && !(otherObject.equals(object)))
					alignOffsetIfObjectCollision(object, offset, otherObject);
			}
		}
	}

	/**
	 * Returns an offset that is aligned to an object
	 * 
	 * @param object
	 * @param offset
	 * @param measureObject
	 * @return
	 */
	private void alignOffsetIfObjectCollision(IGameObject object, Point offset,
			IGameObject measureObject) {
		if (objectsAreColliding(object, offset, measureObject)) { // Check first
			// IF the
			// objects
			// are
			// colliding
			alignOffsetToObject(object, offset, measureObject);
		}
	}

	/**
	 * Aligns an objects offset to another object.
	 * 
	 * @param object
	 * @param offset
	 * @param otherObject
	 * @return
	 */
	public void alignOffsetToObject(IGameObject object, Point offset,
			IGameObject otherObject) {

		// Cache lookup
		objectPosition = object.getPosition();
		otherObjectPosition = otherObject.getPosition();

		if (objectPosition.y + object.getHeight() > otherObjectPosition.y
				&& objectPosition.y < otherObjectPosition.y
						+ otherObject.getHeight())
			if (offset.x > 0) // Check if the object came from the left
				offset.x = otherObjectPosition.x
						- (objectPosition.x + object.getWidth());
			else if (offset.x < 0)
				offset.x = otherObjectPosition.x + // Check if the object came
						// from the right
						otherObject.getWidth() - objectPosition.x;
		if (objectPosition.x + object.getWidth() > otherObjectPosition.x
				&& objectPosition.x < otherObjectPosition.x
						+ otherObject.getWidth())
			if (offset.y > 0) // Check if the object came from above
				offset.y = otherObjectPosition.y
						- (objectPosition.y + object.getHeight());
			else if (offset.y < 0)
				offset.y = otherObjectPosition.y + // Check if the object came
						// from below
						otherObject.getHeight() - objectPosition.y;
	}

	/**
	 * Aligns an offset if it is outside the screens borders.
	 * 
	 * @param object
	 * @param offset
	 *            The new offset that is aligned to the screen.
	 * @return
	 */
	private void alignOffsetIfOutsideScreen(IGameObject object, Point offset) {

		// Cache lookup
		objectPosition = object.getPosition();

		if (objectPosition.x + object.getWidth() + offset.x > screenSize.x
				+ startPos.x) // Align x-axis
			offset.x = (screenSize.x + startPos.x)
					- (objectPosition.x + object.getWidth());
		else if (objectPosition.x + offset.x < startPos.x)
			offset.x = -objectPosition.x + startPos.x;

		if (objectPosition.y + object.getHeight() + offset.y >= screenSize.y
				+ startPos.y) // Align y-axis
			offset.y = (screenSize.y + startPos.y)
					- (objectPosition.y + object.getHeight());
		else if (objectPosition.y + offset.y < startPos.y)
			offset.y = -objectPosition.y + startPos.y;
	}

	/**
	 * Checks whether or not two objects are colliding.
	 * 
	 * @param object
	 *            The first object
	 * @param otherObject
	 *            The object to measure with
	 * @return A boolean.
	 */
	public boolean objectsAreColliding(IGameObject object, Point offset,
			IGameObject otherObject) {
		if (!otherObject.isAlive())
			return false;
		// Cache lookup
		objectPosition = object.getPosition();
		otherObjectPosition = otherObject.getPosition();

		return (objectPosition.y + object.getHeight() + offset.y > otherObjectPosition.y
				&& object.getPosition().y + offset.y < otherObjectPosition.y
						+ otherObject.getHeight()
				&& objectPosition.x + object.getWidth() + offset.x > otherObjectPosition.x && objectPosition.x
				+ offset.x < otherObjectPosition.x + otherObject.getWidth());
	}

	/**
	 * Checks whether or not an object is outside the windows x-axis.
	 * 
	 * @param object
	 * @param offset
	 * @return
	 */
	public boolean objectOutsideWindowXAxis(IGameObject object, Point offset) {
		return ((object.getPosition().x + object.getWidth() + offset.x > screenSize.x) || (object
				.getPosition().x < 0));
	}
	public boolean objectOutsideWindowXAxis(Particle particle, Point offset) {
		return ((particle.getPosition().x + particle.getWidth() + offset.x > screenSize.x) || (particle
				.getPosition().x < 0));
	}

	/**
	 * Checks whether or not an object is outside the windows y-axis.
	 * 
	 * @param object
	 * @param offset
	 * @return
	 */
	public boolean objectOutsideWindowYAxis(IGameObject object, Point offset) {
		return ((object.getPosition().y + object.getHeight() + offset.y > screenSize.y) || (object
				.getPosition().y < 0));
	}
	public boolean objectOutsideWindowYAxis(Particle particle, Point offset) {
		return ((particle.getPosition().y + particle.getHeight() + offset.y > screenSize.y) || (particle
				.getPosition().y < 0));
	}

	/**
	 * Checks whether or not an object is colliding with a specific object.
	 * 
	 * @param object
	 * @param offset
	 * @return
	 */
	public void objectsCollidingWithObject(IGameObject object, Point offset,
			List<IGameObject> collidingObjects) {
		collidingObjects.clear();

		for (int i = 0; i < objects.length; i++) {
			otherObject = objects[i];
			if (!(otherObject.equals(object)))
				if (objectsAreColliding(object, offset, otherObject))
					collidingObjects.add(otherObject);
		}
	}

	public void objectsCollidingWithObject(IGameObject object, Point offset,
			NewList collidingObjects) {
		collidingObjects.clear();

		if (objects != null)
			for (int i = 0; i < objects.length; i++) {
				otherObject = objects[i];
				if (!(otherObject.equals(object)))
					if (objectsAreColliding(object, offset, otherObject))
						collidingObjects.add(otherObject);
			}
	}

}
